#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <signal.h>
#include <pthread.h>

#include "../include/icam.h"

SOCKET serv_skt;
t_icam_cmd icam_beacon_cmd;

INT32S socket_bind(SOCKET skt, struct sockaddr_in *addr)
{
    INT32S ret;

    ret = bind(skt, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
    if (ret != 0) {
        printf("bind error\n");
    }

    return ret;
}

INT32S socket_connect(SOCKET skt, struct sockaddr_in *addr)
{
    INT32S ret;

    ret = connect(skt, (struct sockaddr *) addr, sizeof(struct sockaddr_in));
    if (ret != 0) {
        printf("connect error\n");
    }

    return ret;
}

void icam_set_beacon(void)
{
    memcpy(icam_beacon_cmd.magic, ICAM_CMD_MAGIC, 4);
    icam_beacon_cmd.cmd_id = ICAM_CMD_UPDATE_BEACON;
    icam_beacon_cmd.payload_length = 4;
    *(INT32U *)icam_beacon_cmd.buf = 16;
}

void net_sending_beacon(INT32S signo)
{
    if (send(serv_skt, &icam_beacon_cmd, icam_beacon_cmd.payload_length + ICAM_MAX_CMD_HEAD_SIZE, 0) == -1) {
        debug_printf("Send beacon failed\n");
    }
}

void net_start_sending_beacon(void)
{
    struct itimerval beacon_tmr, old_tmr;

    signal(SIGALRM, net_sending_beacon);

    beacon_tmr.it_value.tv_sec = 1;
    beacon_tmr.it_value.tv_usec = 0;
    beacon_tmr.it_interval.tv_sec = 1;
    beacon_tmr.it_interval.tv_usec = 0;

    setitimer(ITIMER_REAL, &beacon_tmr, &old_tmr);
}

void icam_connect_to_view(INT32U s_addr, INT16U sin_port)
{
    SOCKET viewer_skt;
    struct sockaddr_in local_addr;
    struct sockaddr_in viewer_addr;

    t_icam_cmd icam_cmd;

    viewer_skt = socket(AF_INET, SOCK_DGRAM, 0);

    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(VIEWER_NET_REGISTRY_PORT);
    local_addr.sin_addr.s_addr = htons(INADDR_ANY);
    
    if (socket_bind(viewer_skt, &local_addr) != 0) {
        ///< TODO
        while (1) ;
    }

    viewer_addr.sin_family = AF_INET;
    viewer_addr.sin_port = sin_port;
    viewer_addr.sin_addr.s_addr = s_addr;

    memcpy(icam_cmd.magic, ICAM_CMD_MAGIC, 4);
    icam_cmd.cmd_id = ICAM_CMD_TEST_DATA;
    icam_cmd.payload_length = 0;

    sleep(1);

    // begin to P2P data transfering, 
    // TODO it should be run in a indenpendent thread
    while (1) {
        if (sendto(viewer_skt, &icam_cmd, icam_cmd.payload_length + ICAM_MAX_CMD_HEAD_SIZE, 0, (struct sockaddr *) &viewer_addr, sizeof(viewer_addr)) < 0) {
            debug_printf("viewer connection closed\n");
            close(viewer_skt);
            break;
        }
        debug_printf("sb\n");
        sleep(1);
    }
}

INT32S icam_cmd_get_viewer(INT32U s_addr, INT16U sin_port)
{
    debug_printf("Viewer from: %s:%d\n", 
            inet_ntoa(*((struct in_addr *) &(s_addr))), 
            ntohs(sin_port));
    icam_connect_to_view(s_addr, sin_port);
    return 0;
}

INT32S icam_cmd_parser(t_icam_cmd *cmd)
{
    INT32S ret;

    if (strncmp(cmd->magic, ICAM_CMD_MAGIC, 4) != 0) {
        debug_printf("icam_cmd magic error: %c%c%c%c\n", cmd->magic[0], cmd->magic[1], cmd->magic[2], cmd->magic[3]);
        return -1;
    }

    switch (cmd->cmd_id) {
        case ICAM_CMD_VIEWER_INFO :
            ret = icam_cmd_get_viewer(*(INT32U *)(cmd->buf), *(INT16U *)(cmd->buf + 4));
            break;
        default :
            break;
    }

    return ret;
}

void net_cmd_task(INT32S *result)
{
    struct sockaddr_in local_addr;
    struct sockaddr_in server_addr;
    //struct sockaddr_in viewer_addr;

    struct hostent *host = NULL;

    t_icam_cmd icam_cmd;

    INT32S ret;

    serv_skt = socket(AF_INET, SOCK_DGRAM, 0);

    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(ICAM_NET_SOURCE_PORT);
    local_addr.sin_addr.s_addr = htons(INADDR_ANY);
    
    if (socket_bind(serv_skt, &local_addr) != 0) {
        ///< TODO
        while (1) ;
    }

    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(ICAM_NET_REGISTRY_PORT);
    if ((server_addr.sin_addr.s_addr = inet_addr(ICAM_NET_REGISTRY_NAME)) == INADDR_NONE) {
        host = gethostbyname(ICAM_NET_REGISTRY_NAME);
        if (host == NULL) {
            memcpy(&(server_addr.sin_addr), host->h_addr_list[0], host->h_length);
        } else {
            debug_printf("gethostbyname() faild\n");
            ///< TODO
            while (1) ;
        }
    }

    if (socket_connect(serv_skt, &server_addr) != 0) {
        ///< TODO
        while (1) ;
    }

    net_start_sending_beacon();
   
    while (1) {
        ret = recv(serv_skt, &icam_cmd, ICAM_MAX_CMD_SIZE, 0);
        if (ret == -1) {
            debug_printf("recv cmd error\n");
        } else {
            icam_cmd_parser(&icam_cmd);
        }
    }
}

INT32S net_cmd_task_create(void)
{
    pthread_t id;
    int ret;

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
    ret = pthread_create(&id, &attr, (void *)net_cmd_task, NULL);
    if (ret != 0) {
        debug_printf("net_cmd_thread create faild\n");
    }
    
    return ret;
}

int main(int argc, char *argv[])
{
    icam_set_beacon();

    net_cmd_task_create();

    while (1) {
        ;
    }
    return 0;
}
